package Server; 

import Data.*;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.io.*;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;


public class StationServerImpl implements Runnable
{
	DatagramSocket aSocket = null;
	String MyServerName = null, remoteServer1 = null, remoteServer2 = null, result =null;
	int myPort=0, remoteServer1Port=0, remoteServer2Port=0;

	public static final int[] ports = {6789,6790,6791}; // ports 

			// Concurrent HashMap
			public ConcurrentHashMap <Character, List<Record>> chashtable = new ConcurrentHashMap <Character, List<Record>>();
			
			// static record ids for creating criminal and missing records to maintain uniqueness and also distinguish the records
			static int crec_ID = 20000; // 20000 series for the criminal records
			static int mrec_ID = 40000; // 40000 series for the missing records
					
	public StationServerImpl()
	{
		
	}
	
	public StationServerImpl(String serverName, int port){ 
		
		MyServerName = serverName; //.toLowerCase();
		myPort =port;
		
	}
	
	public String getServerName() {
		
		return MyServerName;
	} // end of getServerName

public String setServerName( int portNo ){
	if (portNo == ports[0])
	{
		MyServerName = "spvm";
		return "SPVM";
	}
	
	if (portNo==ports[1])
	{
		MyServerName = "spl";
		return "SPL";
	}
	
	else 
	{
		MyServerName = "spb";
		return "SPB";
	}
}

public int getPort()
{
	if (MyServerName==this.getServerName())
		return ports[0];
	
	if (MyServerName==this.getServerName())
		return ports[1];
	else
		return ports [2];
	
} // end of getPort()

// Creating a new criminal record for the first time 
	public String createCRecord(String fName, String lName, String desc, String status, String badgeID) {

		String recordID = null;
		
		try{
			// creating a new Criminal record for first time
			recordID = "CR" + ++crec_ID;
			
			// CR parameters ->				recordID, fName, lName, desc, status, badgeID
			Record cr1 = new CriminalRecord(recordID, fName, lName, desc, status, badgeID);
			
			char c= lName.charAt(0);  //key
	
		// if the hashtable contains the Key then we continue to add the Criminal record we created above to the list in the hashtable
		// else we create a new list with the first character of the last name of the criminal and then add it to the hashtable
			if(chashtable.containsKey(lName.charAt(0)))
			{
				List<Record> existingList = chashtable.get(lName.charAt(0));
				
				synchronized(existingList) // synchronize the existing list for that particular character   
				{
					existingList.add(cr1); // add the record to the existing list
					chashtable.put(c, existingList); 
				}
			} // end of if loop
			
		// else we create a new list with the first character of the last name of the criminal and then add it to the chashtable
			else
			{
				List<Record> newList = new ArrayList<Record>();
				synchronized (newList) // synchronize the newly created list and add the record to this newlist
				{
					newList.add(cr1);
					chashtable.put(c, newList);	// insert the new list to the chashtable				
				}
			}// end of else
						
			System.out.println("Creating New Criminal Record... \n");
			
			System.out.println("RecordID    :: " + recordID +
							   "\nFirst Name  :: " + fName +
							   "\nLast Name   :: " + lName +
							   "\nDescription :: " + desc +
							   "\nStatus      :: " + status +
							   "\nBadgeID     :: " + badgeID  );
			
			System.out.println("\n1 New Criminal Record Created \n \n");
	//		System.out.println("MyServerName in CR : " + MyServerName);
			// create a log entry in the server
		
			this.CreateServerLogs("Criminal Record created. ", recordID, fName, lName, desc, status, badgeID, MyServerName);
			
			// preparing the result back
			
			result = "{code:100, message:Added record., data:{recordID:"+ cr1.getRecordID() + 
									",firstName:" + cr1.getFirstName() + 
									",lastName:" + cr1.getLastName() + 
									",status:" + cr1.getStatus() + 
									",crimeDescription:" + ((CriminalRecord) cr1).getDesc() + "}}";
			
		} // end of try for createCRecord
		
		catch (Exception e){
			e.printStackTrace();
		} // end of catch for createCrecord
		
		return result;
				
	} // end of createCRecord

	
// Creating a new missing record for the first time 
	public String createMRecord(String fName, String lName, String addr, String lDate, String lastLoc, String status, String badgeID) {
		
		String recordID = null;
		
		try
		{
			recordID = "MR" + ++mrec_ID; 
		//MR Parameters -> RecordID, fName, lName, addr, lDate, lastLoc, status, BadgeID
			Record mrec = new MissingRecord(recordID, fName, lName, addr, lDate, lastLoc, status, badgeID);
			if(chashtable.containsKey(lName.charAt(0)))
			{
				List<Record> existingList = chashtable.get(lName.charAt(0));
				synchronized (existingList) 
				{
					existingList.add(mrec);
					chashtable.put(lName.charAt(0),existingList);
				}
				
			}// end of if loop
			
			else
			{
				
				List<Record> newList = new ArrayList<Record>();
				synchronized (newList) // synchronize the newly created list and add the record to this newlist
				{
					newList.add(mrec);
					chashtable.put(lName.charAt(0), newList);	// insert the new list to the chashtable				
				}
				
			} // end of else loop
						
			System.out.println("Creating New Missing Record... \n");
			
			System.out.println("RecordID     	   :: "   + recordID +
							   "\nFirst Name     	 :: " + fName +
							   "\nLast Name      	 :: " + lName +
							   "\nLast known Address :: " + addr +
							   "\nDate Last seen 	 :: " + lDate +
							   "\nBadgeID       	 :: " + badgeID  );
			
			System.out.println("1 New Missing Record Created \n \n");

			// create a log entry in the server
			this.CreateServerLogs("Missing Record created.", recordID, fName, lName, addr, status, badgeID, this.MyServerName);
		
			// preparing the result 
			result = "{code:200, message:Added record., data:{recordID:" + mrec.getRecordID() + 
					",firstName:" + mrec.getFirstName() + 
					",lastName:" + mrec.getLastName() +
					",status:" + mrec.getStatus() + 
					",address:"	+ ((MissingRecord) mrec).getAddress() + 
					",lastDate:" + ((MissingRecord) mrec).getLastDate() +
					",lastLocation:" + ((MissingRecord) mrec).getLastLocation() + "}}";
			
		} // end of try block
		
		catch(Exception e){
			e.printStackTrace();
		} // end of catch block
		
		return result;
	}  // end of createMRecord

	
	// Editing an existing missing or criminal missing record 
	public String editRecord(String lName, String recordID, String newStatus, String badgeID) {
		
		boolean flag = false; // flag is used to check whether the status for the record was updated or not.
		
		try{
			
				// first check for the recordID length. Record length should be exactly 7 characters  
			if( (recordID == null) || (recordID.length()!= 7) )
			{
				System.out.println("Invalid Record ID entered by the Police Officer! \n RecordID :: "+ recordID);
				result= "{code:-100, message:Record not found.}";
			}// end if loop
	
			// if the recordID is correct and record exists then get the list in which record is present in the chashtable
			List<Record> editlist = chashtable.get(lName.charAt(0));
	
			// if the list is empty then the record is not present; hence could not be edited
			if(editlist == null)
			{
				System.out.println("List begining with character \"" +  lName.charAt(0) + "\" was not found. ");
				result= "{code:-100, message:Record not found.}";
			} // end if loop
	
			// traverse the list to match the recordID with the list and if found, update the new the status for the record 
			int index=0;
			for ( Record rec : editlist)  
			{  
				if(rec.getRecordID().equals(recordID))
				{
					editlist.get(index).updateStatus(newStatus);
					flag= true;
					System.out.println(recordID + " :: was edited successfully. Status now changed to "+ newStatus);
					
					result = "{code:400, message:Edited record., data:{recordID:" + recordID + 
							",firstName:" + editlist.get(index).getFirstName() + 
							",lastName:" + editlist.get(index).getLastName() +
							",status:" + newStatus + 
							",crimeDescription:" + ((CriminalRecord) editlist.get(index)).getDesc() + "}}";
										
				} // end of if loop for record editing successful
				
        	} // end for loop for traversing the records within a list.
		
			// if record is not successfully updated then shoot an error message
			if(!flag)
			{
				System.out.println(recordID +" :: could not be modified! Please try again later\n");
				result = "{code:-100, message:Record not found.}";
			}
			
		} // end of try block
		
		catch(Exception e)
		{
			e.printStackTrace();
		} // end of exception
		return result;
		

	} // end of editRecord
	
// Returns the record count from all the three server stations
	public String getRecordCounts() {
			System.out.println("IN getRecordCounts() :  "+this.getPort()+ "  "+ this.getServerName()+"\n\n");
					
		// ports = {6789,6790,6791}; spvm, spl, spb
		
		// When the station server of SPVM calls record count then we make a call to other two station servers using UDP and return the count of each
		// of the servers. Similarly we do it for all the 3 servers depending on who calls the getRecordCounts method.
		if (this.getPort() == ports[0]){
			
			MyServerName = "SPVM";
			remoteServer1Port = ports[1];
			remoteServer1 = "SPL";
			remoteServer2Port = ports[2];
			remoteServer2 = "SPB";
		
			System.out.println(this.getPort());
			
		}// end if for spvm
		
		if (this.getPort()== ports[1]){
		
			MyServerName = "SPL";
			remoteServer1Port = ports[0];
			remoteServer1 = "SPVM";
			remoteServer2Port = ports[2];
			remoteServer2 = "SPB";
			
			System.out.println(this.getPort());
			
		}// end if for spl
		
		if (myPort == ports[2]){
			
			MyServerName = "SPB";
			remoteServer1Port = ports[0];
			remoteServer1 = "SPVM";
			remoteServer2Port = ports[1];
			remoteServer2 = "SPL";
		
			System.out.println(this.getPort());
			
		} // end if for spb
		
		System.out.println(remoteServer1+ "\t" + remoteServer1Port + "\t" +remoteServer2 +"\t"+ remoteServer2Port);
		String totalRecords = getTotalCountFromMyServer(this.getServerName(),this.getPort());
		
		System.out.println(totalRecords);
		
		//System.out.println(MyServerName + " : " + totalRecords +"  ");
		udpRequest(remoteServer1, remoteServer1Port, remoteServer2, remoteServer2Port);
		
		return result;
	} // end of getRecordCounts

	
	public synchronized String getTotalCountFromMyServer(String serverName, int portNo)	{
		int count = 0;
		
		System.out.println("ServerName:: " +this.getServerName());
		System.out.println("Port#:: " +this.getPort());
		
		for(Character key : chashtable.keySet())
		{
			count += this.chashtable.get(key).size();
		}
		
		return "Server Name :: " + this.getServerName()  + "  "+count + " records ";
	} 
	
	// udpRequest to the server for inter- server communication
	// behaves as the UDP Client
	private void udpRequest(String remoteServer1 , int remoteServer1Port, String remoteServer2, int remoteServer2Port)
	{

		String[] serverNames=new String[2];
		serverNames[0] = remoteServer1;
		serverNames[1] = remoteServer2;
		
		int [] portNos = {remoteServer1Port,remoteServer2Port};
		
	//	System.out.println(serverNames[0] +"  "+ serverNames[1] +"  " +  portNos[0] +"  " + portNos[1]);
		
	//	System.out.println(StationServerImpl.ports.length);
		
		for(int i =0; i<(StationServerImpl.ports.length - 1);i++)
		{
			DatagramSocket aSocket = null;
			DatagramPacket request=null, reply=null;
			
			try{
				
				aSocket = new DatagramSocket();
			
				byte [] servername = serverNames[i].getBytes();
				InetAddress aHost  = InetAddress.getByName("localhost");
				request = new DatagramPacket(servername, servername.length, aHost ,portNos[i]);
				aSocket.send(request);
				
				byte [] remoteServerCount = new byte[1024];
				reply = new DatagramPacket(remoteServerCount, remoteServerCount.length);
				aSocket.receive(reply);
				System.out.println(serverNames[i]+ " : " + new String(reply.getData()) +"  ");			
				
			} // end of try block
			
			catch(SocketException sExp){
				System.out.println("Socket Exception:" +sExp.getMessage());
			} // end of catch block for SocketException
			
			catch(IOException ioExp){
				System.out.println("IO Exception: "+ioExp.getMessage());
			} // end of catch block for IOException
			
			finally {
				if(aSocket != null)
					aSocket.close(); 
				} // end of finally block
			
		} // end of finally block
		
	}// end of udpRequest


	// UDP for Transfer Record Method
	public void callUDP()
	{
		new UDPThread(this,this.myPort).start();
	} // end of UDP for Transfer Record Method

	
	// transfer Record method
	public boolean transferRecord(String badgeId, String recordId, String ss) {

		// Call the UDP server for transfer record
		// callUDP();
		String detailsToTransfer = null;
		String source = badgeId.substring(0, 2);
		
		if(source.startsWith("SPV")){System.out.println("Request received from " + ports[0]);  }
		if(source.startsWith("SPL")){System.out.println("Request received from " + ports[1]);  }
		if(source.startsWith("SPB")){System.out.println("Request received from " + ports[2]);  }
		
		//String dest = ss.getStationServerName().substring(0, 3);
		int portToSend = 0;
		
        if(ss.equals("SPVM"))
        	portToSend = ports[0];

        if(ss.equals("SPL"))
       		portToSend = ports[1];
       
		if(ss.equals("SPB"))
        	portToSend = ports[2];
		
		//Record rec = new MissingRecord();
		
		//String recordToTransfer = record.recordID + record.fName + record.lName + record.status;
		
		//Record recToTransfer = this.getSelectedRecord(record);
		Set<Character> k=chashtable.keySet(); 
		for(Character c:k)
		{
			List<Record> l=chashtable.get(c);
			
			for(int i=0;i<l.size();i++)
			{
				if(l.get(i).getRecordID().equals(recordId))
				{
					System.out.println("record ID matched");
					
					// if criminal record / missing record then parameters will vary.
					if(recordId.startsWith("CR")){
						System.out.println("Criminal Record :"+ recordId  + "to be transfered.");
						
						//recordID, fName, lName, desc, status, badgeID
						
					//	CriminalRecord cr1 = new CriminalRecord(recordId, l.get(i).fName, l.get(i).lName, desc, status, badgeID);
						
					    String recordID = l.get(i).getRecordID();
						String fName = l.get(i).getFirstName();
						String lName = l.get(i).getLastName();
						String status = l.get(i).getStatus();
						String desc = ((CriminalRecord) l.get(i)).getDesc();
						
						detailsToTransfer = recordId+","+fName+","+lName+","+status+","+desc;
						
						boolean t= UDPTransferRequest(detailsToTransfer, portToSend);
						
						if(t)
						{
							System.out.println("Record : "+ recordID + " has been successfully transfered to Station" + ss);
							chashtable.remove(i);
						}
						
						else {
							System.out.println("Criminal Record could not be transferred :-( Please try again later!");
						}
						
					}
					
					//recordID, fName, lName, addr, lDate, lastLoc, status, badgeID
					if(recordId.startsWith("MR")) {
						System.out.println("Missing Record : " + recordId + " to be transfered");
							
					    String recordID = l.get(i).getRecordID();
						String fName = l.get(i).getFirstName();
						String lName = l.get(i).getLastName();
						String addr = ((MissingRecord) l.get(i)).getAddress();
						String lDate =((MissingRecord) l.get(i)).getLastLocation();
						String status = l.get(i).getStatus();
						
						detailsToTransfer = recordId+","+fName+","+lName+","+addr+","+lDate+","+status;
						
						boolean t= UDPTransferRequest(detailsToTransfer, portToSend);
						
						if(t)
						{
							System.out.println("Record : "+ recordID + " has been successfully transfered to Station" + ss);
							chashtable.remove(i);
							
						}
						
						else {
							System.out.println("Missing Record could not be transferred :-( Please try again later!");
						}
						
						
					} // end of MR
										
				//	detailsToTransfer=l.get(i).getRecordID()+l.get(i).getFirstName()+l.get(i).getLastName()+l.get(i).getStatus();
					
					System.out.println(detailsToTransfer+ " :" + "port to send : "+ portToSend);
										
					
				} // end if
			} // end inner for loop
		} // end outer for loop
		
		return true;

	}// end TransferRecord Method
	
	
	public boolean UDPTransferRequest(String detailsToTransfer, int portToSend) {
		
		//	DatagramSocket aSocket = null;  
	        boolean transferSuccess = false;
	        
	        try {
	        	System.out.println("In UDPTransfer Request Method!\n");
	        	aSocket = new DatagramSocket();
	        	byte[] b = detailsToTransfer.getBytes();
	        	InetAddress aHost = InetAddress.getByName("localhost");
	        	DatagramPacket request = new DatagramPacket(b,detailsToTransfer.length(), aHost, portToSend);
	        	
	        	aSocket.send(request);
	        	System.out.println("Request is sent to port : " + portToSend);
	        	
	        	byte[] buffer = new byte[55];
	        	DatagramPacket response = new DatagramPacket(buffer, buffer.length);
	          
	          	aSocket.receive(response);
	          	System.out.println("Request is received at port : " + portToSend);
	          	
	            String responseString = new String(response.getData());
	            System.out.println(responseString);

	            String u= responseString;
	            System.out.println("Response: " + responseString);

	            transferSuccess = true;
	        }
	        
	        catch(Exception e){
	        	
	        	e.printStackTrace();
	        }
	     
	        finally {
	        	
	  	        if (aSocket != null)
	  	        	aSocket.close();
	         	}
	        			
	       	return (transferSuccess);
	        	   
		}

	// Maintaining the logs of operations performed on the records at various stations stored in each file individually
	private boolean CreateServerLogs(String actionTaken, String recordID, String fName, String lName, String desc, 
			String status, String badgeID ,String serverName){
    	
		File logfile; 
		BufferedWriter bw = null;
    	Date today = new Date();
      	
    	try{
    		
    	//	System.out.println(this.MyServerName +" in createServerLogs()");
    		
    	// for each station server we have to maintain one file containing all records
    		System.out.println(this.MyServerName );
    		logfile = new File("C:\\Users\\Salim\\workspace_3\\Server\\Server_"+ this.MyServerName +".txt"); // file name : Server_serverName.txt
    		logfile.createNewFile();
    		
    		FileWriter fw = new FileWriter(logfile.getName(), true);
    		bw = new BufferedWriter(fw);
    		
    		String details = "RecordID:: " + recordID + " First Name:: " + fName + " Last Name:: " + lName +
					   " Description:: " + desc + " Status:: " + status + " BadgeID:: " + badgeID + " Date::" + today + 
					   "ActionTaken:: " + actionTaken + "My SERVER :: " +this.MyServerName ;
    		
    		bw.write(details); // write the details onto the file.
    		bw.newLine();
    		
    		System.out.println("Record "+ recordID + "is added to the Log file of the server.\n\n");
    	}// end of the try block
    	
    	catch(Exception e)
    	{
    		System.out.println("Could not write log for the Record "+ recordID);
    		e.printStackTrace();
    	}// end of exception
    	
    	// close the file after writing the logs
    	try
    	{
    		bw.close();
    	}// end try block for closing the file
    	catch (Exception ee)
    	{
    		ee.printStackTrace();
    	} // end catch block for closing the file
    	
		return true;
	} // end of CreateServerLogs

	@Override
	public void run() {
		
		
		// TODO Auto-generated method stub
		
		try {
			aSocket = new DatagramSocket(myPort);
			byte[] buffer = new byte[5000];
			
			while(true){
				
				DatagramPacket request = new DatagramPacket(buffer, buffer.length);
				aSocket.receive(request);

				String requestMessage = new String(request.getData()).trim();
				System.out.println(requestMessage);
				List<String> arraylist = Arrays.asList(requestMessage.split(","));

				byte[] msg = null;
				String replyMessage = null;
				
				// case 1: create a Criminal record
				// String fName, String lName, String desc, String status, String badgeID
				if (arraylist.get(0).equals("1")) {
					replyMessage = this.createCRecord(arraylist.get(1), arraylist.get(2), arraylist.get(3), arraylist.get(4), arraylist.get(5));
					System.out.println(replyMessage);
				} // end if for case 1.
				
				else 
					if (arraylist.get(0).equals("2")) {
					replyMessage = this.createMRecord(arraylist.get(1),
							arraylist.get(2), arraylist.get(3), arraylist.get(4),
							arraylist.get(5), arraylist.get(6), arraylist.get(7));
					
					System.out.println(replyMessage);
					} // end if for case 2.
				
				
				msg = replyMessage.getBytes();
				DatagramPacket reply = new DatagramPacket(msg, msg.length, request.getAddress(), request.getPort());
				aSocket.send(reply);
			}// end of while true

		}// end of try block
		
		 catch (SocketException e) 
		 {
			System.out.println("Socket Exception : " + e.getMessage());
		 } // end catch block for socket exception 
		
		catch (IOException e)
		{
				System.out.println("IO Exception : " + e.getMessage());
		}// end catch block for IO Exception
		
		finally 
		{
			if(aSocket != null)
				aSocket.close(); // close the Socket 
		}// end of Finally block
		
	} // end of run()



}